package com.github.smartheye.fabric.performance.spring.service.impl;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.PostConstruct;

import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.Channel.PeerOptions;
import org.hyperledger.fabric.sdk.Channel.TransactionOptions;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.Orderer;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.SDKUtils;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.Peer.PeerRole;
import org.hyperledger.fabric.sdk.ProposalPeerFutureSet;
import org.hyperledger.fabric.sdk.TransactionRequest.Type;
import org.hyperledger.fabric.sdk.TransactionRequest;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.springframework.stereotype.Service;

import com.github.smartheye.fabric.common.FabricTransactionProposalRequest;
import com.github.smartheye.fabric.common.util.UserUtil;
import com.github.smartheye.fabric.performance.spring.service.HyperledgerService;

@Service
public class HyperledgerServiceListenerImpl implements HyperledgerService {

	private static final String CHANNEL_NAME = "mychannel";
	private HFClient hfClient;
	private Channel channel;
	private Collection<Peer> endorsorPeer = new ArrayList<Peer>();
	private List<Orderer> orderers = new ArrayList<Orderer>(4);
	private Orderer[] ordererArrays = new Orderer[4];

	@PostConstruct
	public void init() throws Exception {
		hfClient = HFClient.createNewInstance();
		hfClient.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());

		String adminPrivateKey = "classpath:/admin/58cd048b98913fb7646ecadb1e93bfe96e74de16d6043b2820a3ed6b37fa9c66_sk";
		String signcerts = "classpath:/admin/Admin@org1.example.com-cert.pem";
		User user = UserUtil.loadUser(adminPrivateKey, signcerts);
		hfClient.setUserContext(user);

		channel = hfClient.newChannel(CHANNEL_NAME);
		// Initializing Peer
		Properties peerProperties = new Properties();
		Peer peer0 = hfClient.newPeer("peer0.org1.example.com", "grpc://localhost:7051", peerProperties);
		Peer peer1 = hfClient.newPeer("peer1.org1.example.com", "grpc://localhost:8051", peerProperties);
		Peer peer2 = hfClient.newPeer("peer2.org1.example.com", "grpc://localhost:9051", peerProperties);
		Peer peer3 = hfClient.newPeer("peer3.org1.example.com", "grpc://localhost:10051", peerProperties);
		Peer peer4 = hfClient.newPeer("peer4.org1.example.com", "grpc://localhost:11051", peerProperties);
		Peer peer5 = hfClient.newPeer("peer0.org2.example.com", "grpc://localhost:12051", peerProperties);
		channel.addPeer(peer0,
				PeerOptions.createPeerOptions() //
						.setPeerRoles(PeerRole.ALL) //
						.registerEventsForBlocks() //
						.startEventsNewest());
		channel.addPeer(peer1, PeerOptions.createPeerOptions() //
				.setPeerRoles(PeerRole.NO_EVENT_SOURCE));
		channel.addPeer(peer2, PeerOptions.createPeerOptions() //
				.setPeerRoles(PeerRole.NO_EVENT_SOURCE));
		channel.addPeer(peer3, PeerOptions.createPeerOptions() //
				.setPeerRoles(PeerRole.NO_EVENT_SOURCE));
		channel.addPeer(peer4, PeerOptions.createPeerOptions() //
				.setPeerRoles(PeerRole.NO_EVENT_SOURCE));
		channel.addPeer(peer5, PeerOptions.createPeerOptions() //
				.setPeerRoles(EnumSet.of(PeerRole.CHAINCODE_QUERY, PeerRole.LEDGER_QUERY)));

		endorsorPeer.add(peer0);
		endorsorPeer.add(peer1);
		endorsorPeer.add(peer2);
		endorsorPeer.add(peer3);
		endorsorPeer.add(peer4);

		Properties ordererProperties = new Properties();
		Orderer orderer1 = hfClient.newOrderer("orderer1.example.com", "grpc://localhost:7050", ordererProperties);
		Orderer orderer2 = hfClient.newOrderer("orderer2.example.com", "grpc://localhost:8050", ordererProperties);
		Orderer orderer3 = hfClient.newOrderer("orderer3.example.com", "grpc://localhost:9050", ordererProperties);
		Orderer orderer4 = hfClient.newOrderer("orderer4.example.com", "grpc://localhost:10050", ordererProperties);

		channel.addOrderer(orderer1);
		channel.addOrderer(orderer2);
		channel.addOrderer(orderer3);
		channel.addOrderer(orderer4);

		orderers.add(orderer1);
		orderers.add(orderer2);
		orderers.add(orderer3);
		orderers.add(orderer4);

		ordererArrays[0] = orderer1;
		ordererArrays[1] = orderer2;
		ordererArrays[2] = orderer3;
		ordererArrays[3] = orderer4;

		channel.initialize();
		System.out.println(CHANNEL_NAME + "初始化成功");
	}

	AtomicLong count = new AtomicLong(0);

	Object monitor = new Object();

	@Override
	public void invokeSet1Value() throws Exception {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
		String prefix = sdf.format(new Date());
		long seqNo = count.incrementAndGet();
		if (seqNo > 9999999) {
			synchronized (monitor) {
				if (count.get() > 9999999) {
					count.set(0);
				}
				seqNo = count.incrementAndGet();
			}
		}
		String seqNoStr = String.format("%08d", seqNo);
		String key = prefix + seqNoStr;
		// System.out.println("key="+key);

		TransactionProposalRequest request = new FabricTransactionProposalRequest(hfClient.getUserContext());
		request.setChaincodeLanguage(Type.GO_LANG);
		request.setFcn("set");
		request.setArgs(key, "1");
		request.setChaincodeID(ChaincodeID.newBuilder().setName("mycc").setVersion("1.0").build());
		request.setProposalWaitTime(3000);

		Collection<Peer> endorsingPeers = channel.getPeers(EnumSet.of(PeerRole.ENDORSING_PEER));
		try {
			GrpcPeerCallListenerImpl listener = new GrpcPeerCallListenerImpl();
			listener.setPeers(endorsingPeers);
			listener.setHFClient(hfClient);
			
			ProposalPeerFutureSet futureSet = channel.sendTransactionProposalAsyncWithListener(request, endorsingPeers, listener);

			List<ProposalResponse> responses = listener.get(request.getProposalWaitTime(), TimeUnit.MICROSECONDS);
			
			Collection<Set<ProposalResponse>> conistencyResponseList = SDKUtils.getProposalConsistencySets(responses);
			Set<ProposalResponse> conistencyResponse = null;
			for (Iterator iterator = conistencyResponseList.iterator(); iterator.hasNext();) {
				Set<ProposalResponse> set = (Set<ProposalResponse>) iterator.next();
				if (conistencyResponse == null || set.size() > conistencyResponse.size()) {
					conistencyResponse = set;
				}
			}
			if (conistencyResponse == null) {
				System.err.println("没有得到正确的提案結果");
				throw new RuntimeException("没有得到正确的提案結果");
			}
			Orderer orderer = getOrderer(seqNo);
			List<Orderer> targetOrderer = new ArrayList<Orderer>();
			targetOrderer.add(orderer);
			channel.sendTransaction(conistencyResponse, targetOrderer);
		} catch (ProposalException e) {
			e.printStackTrace();
			throw e;
		} catch (InvalidArgumentException e) {
			e.printStackTrace();
			throw e;
		}
	}

	public Orderer getOrderer(long index) {
		int num = ((int) index) % 4;
		return ordererArrays[num];
	}
}
